package com.wk.util;

import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.ByteBuffer;
import java.util.Map;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 二进制数据存取工具类.
 * 
 * @author qiushaohua
 * 
 */
public final class BinaryDataUtil {

	/** 字节缓存大小. */
	private static final int BUF_SIZE = 1024;

	/** 私有化. */
	private BinaryDataUtil() {
	}

	/**
	 * 读取文件内容.
	 * 
	 * @param file
	 *            文件
	 * @return 文件内容
	 */
	public static byte[] readFileData(final File file) {
		byte[] ret = null;
		FileInputStream fis = null;
		try {
			fis = new FileInputStream(file);
			ret = new byte[fis.available()];
			final int count = fis.read(ret);
			if (count != ret.length) {
				throw new RuntimeException("读取文件内容不完整");
			}
		} catch (Exception e) {
			if (e instanceof RuntimeException) {
				throw (RuntimeException) e;
			}
			throw new RuntimeException(e);
		} finally {
			close(fis);
		}
		return ret;
	}

	/**
	 * 读取流.
	 * 
	 * @param in
	 *            流
	 * @return 数据
	 * @throws IOException
	 *             读取出现异常
	 */
	public static byte[] toByteArray(final InputStream in) throws IOException {
		if (in == null) {
			return null;
		}
		final ByteBuffer buf = ByteBuffer.allocate(in.available());

		final byte[] tmp = new byte[BUF_SIZE];
		for (int cnt = in.read(tmp); cnt != -1; cnt = in.read(tmp)) {
			buf.put(tmp, 0, cnt);
		}

		return buf.array();
	}

	/**
	 * 把文件中的数据写入到Http响应.
	 * 
	 * @param dataType
	 *            数据类型，即文件名称后缀
	 * @param file
	 *            文件
	 * @param request
	 *            http请求
	 * @param response
	 *            httpResponse
	 */
	public static void writeToStream(final String dataType, final File file, final HttpServletRequest request,
			final HttpServletResponse response) {
		fileToResponse(dataType, file, request, response, false);
	}

	/**
	 * 下载文件..
	 * 
	 * @param dataType
	 *            数据类型，即文件名称后缀
	 * @param file
	 *            文件
	 * @param request
	 *            http请求
	 * @param response
	 *            httpResponse
	 */
	public static void download(final String dataType, final File file, final HttpServletRequest request,
			final HttpServletResponse response) {
		fileToResponse(dataType, file, request, response, false);
	}

	/**
	 * 把文件中的数据写入到Http响应.
	 * 
	 * @param filename
	 *            文件名称
	 * @param file
	 *            文件
	 * @param request
	 *            http请求
	 * @param response
	 *            httpResponse
	 * @param isDownload
	 *            是否添加文件下载的header
	 */
	private static void fileToResponse(final String filename, final File file, final HttpServletRequest request,
			final HttpServletResponse response, final boolean isDownload) {
		if (!file.exists()) {
			return;
		}
		if (isDownload) {
			addDownloadHeader(request, response, filename);
		}
		addLengthHeader(response, (int) file.length());
		response.setContentType(request.getSession().getServletContext().getMimeType(filename));
		FileInputStream fis = null;
		ServletOutputStream sos = null;
		try {
			fis = new FileInputStream(file);
			sos = response.getOutputStream();
			final byte[] buffer = new byte[BUF_SIZE];
			int readCount = 0;
			while ((readCount = fis.read(buffer)) > 0) {
				sos.write(buffer, 0, readCount);
			}
		} catch (FileNotFoundException e) {
			throw new RuntimeException(e);
		} catch (IOException e) {
			throw new RuntimeException(e);
		} finally {
			close(fis);
			close(sos);
		}
	}

	/**
	 * 把二进数据写入到Http响应流.
	 * 
	 * @param filename
	 *            数据类型，即文件名称后缀
	 * @param data
	 *            大字段
	 * @param request
	 *            http请求
	 * @param response
	 *            数据流
	 */
	public static void writeToStream(final String filename, final byte[] data, final HttpServletRequest request,
			final HttpServletResponse response) {
		dataToResponse(filename, data, request, response, false);
	}

	/**
	 * 把二进数据作为文件进行下载.
	 * 
	 * @param filename
	 *            数据类型，即文件名称后缀
	 * @param data
	 *            大字段
	 * @param request
	 *            http请求
	 * @param response
	 *            数据流
	 */
	public static void download(final String filename, final byte[] data, final HttpServletRequest request,
			final HttpServletResponse response) {
		dataToResponse(filename, data, request, response, true);
	}

	/**
	 * 把二进数据写入到Http响应流.
	 * 
	 * @param filename
	 *            数据类型，即文件名称后缀
	 * @param data
	 *            大字段
	 * @param request
	 *            http请求
	 * @param response
	 *            数据流
	 * @param isDownload
	 *            是否添加文件下载的header
	 */
	private static void dataToResponse(final String filename, final byte[] data, final HttpServletRequest request,
			final HttpServletResponse response, final boolean isDownload) {
		if (data == null || data.length == 0) {
			return;
		}
		if (isDownload) {
			addDownloadHeader(request, response, filename);
		}
		addLengthHeader(response, data.length);
		response.setContentType(request.getSession().getServletContext().getMimeType(filename));
		ServletOutputStream sos = null;
		try {
			sos = response.getOutputStream();
			sos.write(data);
		} catch (FileNotFoundException e) {
			throw new RuntimeException(e);
		} catch (IOException e) {
			throw new RuntimeException(e);
		} finally {
			close(sos);
		}
	}

	/**
	 * 把二进数据写入到Http响应流.
	 * 
	 * @param filename
	 *            数据类型，即文件名称后缀
	 * @param data
	 *            大字段
	 * @param request
	 *            http请求
	 * @param response
	 *            数据流
	 */
	public static void writeToStream(final String filename,final long filseSize,final InputStream data, final HttpServletRequest request,
			final HttpServletResponse response) {
		dataToResponse(filename,filseSize,data, request, response, false);
	}

	/**
	 * 把二进数据作为文件进行下载.
	 * 
	 * @param filename
	 *            数据类型，即文件名称后缀
	 * @param data
	 *            大字段
	 * @param request
	 *            http请求
	 * @param response
	 *            数据流
	 */
	public static void download(final String filename,final long fileSize,final InputStream data, final HttpServletRequest request,
			final HttpServletResponse response) {
		dataToResponse(filename,fileSize,data, request, response, true);
	}

	/**
	 * 把二进数据写入到Http响应流.
	 * 
	 * @param filename
	 *            数据类型，即文件名称后缀
	 * @param data
	 *            大字段
	 * @param request
	 *            http请求
	 * @param response
	 *            数据流
	 * @param isDownload
	 *            是否添加文件下载的header
	 */
	private static void dataToResponse(final String filename,final long fileSize,final InputStream data, final HttpServletRequest request,
			final HttpServletResponse response, final boolean isDownload) {
		if (data == null) {
			return;
		}
		if (isDownload) {
			addDownloadHeader(request, response, filename);
		}
		response.setContentType(request.getSession().getServletContext().getMimeType(filename));
		ServletOutputStream sos = null;
		try {
			
			if (fileSize > 0) {
				addLengthHeader(response, fileSize);
			}

			sos = response.getOutputStream();
			final byte[] buffer = new byte[BUF_SIZE];
			int readCount = 0;
			while ((readCount = data.read(buffer)) > 0) {
				sos.write(buffer, 0, readCount);
			}
		} catch (FileNotFoundException e) {
			throw new RuntimeException(e);
		} catch (IOException e) {
			throw new RuntimeException(e);
		} finally {
			close(sos);
		}
	}

	/**
	 * 把二进数据写入到Http响应流.
	 * 
	 * @param filename
	 *            数据类型，即文件名称后缀
	 * @param data
	 *            大字段
	 * @param request
	 *            http请求
	 * @param response
	 *            数据流
	 */
	public static void writeToStream(final Map<String, Object> fileInfo, final InputStream data,
			final HttpServletRequest request, final HttpServletResponse response) {
		dataToResponse(fileInfo, data, request, response, false);
	}

	/**
	 * 把二进数据作为文件进行下载.
	 * 
	 * @param filename
	 *            数据类型，即文件名称后缀
	 * @param data
	 *            大字段
	 * @param request
	 *            http请求
	 * @param response
	 *            数据流
	 */
	public static void download(final Map<String, Object> fileInfo, final InputStream data,
			final HttpServletRequest request, final HttpServletResponse response) {
		dataToResponse(fileInfo, data, request, response, true);
	}

	/**
	 * 把二进数据写入到Http响应流.
	 * 
	 * @param filename
	 *            数据类型，即文件名称后缀
	 * @param data
	 *            大字段
	 * @param request
	 *            http请求
	 * @param response
	 *            数据流
	 * @param isDownload
	 *            是否添加文件下载的header
	 */
	private static void dataToResponse(final Map<String, Object> fileInfo, final InputStream data,
			final HttpServletRequest request, final HttpServletResponse response, final boolean isDownload) {
		if (data == null) {
			return;
		}
		final String filename = (String) fileInfo.get("filename");
		if (isDownload) {
			addDownloadHeader(request, response, filename);
		}
		response.setContentType(request.getSession().getServletContext().getMimeType(filename));
		ServletOutputStream sos = null;
		try {
			final Long length = (Long) fileInfo.get("length");
			if (length != null) {
				addLengthHeader(response, length);
			} else if (data.available() > 0) {
				addLengthHeader(response, data.available());
			}
	
			sos = response.getOutputStream();
			final byte[] buffer = new byte[BUF_SIZE];
			int readCount = 0;
			while ((readCount = data.read(buffer)) > 0) {
				sos.write(buffer, 0, readCount);
			}
		} catch (FileNotFoundException e) {
			throw new RuntimeException(e);
		} catch (IOException e) {
			throw new RuntimeException(e);
		} finally {
			close(sos);
		}
	}

	/**
	 * 关闭.
	 * 
	 * @param closable
	 *            可关闭对象
	 */
	public static void close(final Closeable closable) {
		try {
			if (closable != null) {
				closable.close();
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * 添加文件下载的Header.
	 * @param request Http请求
	 * @param response
	 *            Http响应
	 * @param filename
	 *            下载显示的文件名
	 */
	public static void addDownloadHeader(final HttpServletRequest request, final HttpServletResponse response, final String filename) {
		try {
			response.setHeader("Content-Disposition", "attachment;filename=\""
					+ encodeFilenameForDownload(request, filename) + "\"");
		} catch (UnsupportedEncodingException e) {
			throw new RuntimeException(e);
		}
	}


	/**
	 * 对下载的文件名进行编码处理
	 * @param request
	 * @param filename
	 * @return
	 * @throws UnsupportedEncodingException
	 */
	public static String encodeFilenameForDownload(final HttpServletRequest request, final String filename)
			throws UnsupportedEncodingException {
		// Chrome: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2191.0 Safari/537.36
		// Firefox: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:30.0) Gecko/20100101 Firefox/30.0
		// IE11: Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; Touch; rv:11.0) like Gecko
		// IE10: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Trident/6.0)
		
		final String userAgent = request.getHeader("User-Agent");
		if (userAgent == null || userAgent.indexOf("Trident") == -1) {
			return new String(filename.getBytes(), "iso8859-1");
		}
		// 针对IE 进行不同的处理
		return URLEncoder.encode(filename, "UTF-8").replaceAll("\\+", "%20");
	}

	/**
	 * 添加文件下载的Header.
	 * 
	 * @param response
	 *            Http响应
	 * @param size
	 *            大小
	 */
	public static void addLengthHeader(final HttpServletResponse response, final long size) {
		response.addHeader("Content-Length", size + "");
	}
}
